import { EcsEntity, EntityPool } from "./EcsEntity";
import { EcsEntityGroup } from "./EcsEntityGroup";
import { EcsComponent } from "./EcsComponent";
import { fixNumber } from "./Utils";
import { EcsSystem } from "./EcsSystem";


export abstract class EcsWorld {
	nextEntityGenId: number = 10000;
	/**剩余待计入流逝的时间 */
	public leftElapsed: number = 0;
	protected _elapsed_per_frame: number = 20;
	protected _exit: boolean = false;

	public entities: { [key: number]: EcsEntity } = {};

	public singletons: any[] = [];

	public singletonsMap = new Map<Object, any>();

	/**实体map池 */
	private _entityPoolMap: { [key: string]: EntityPool } = {};

	/**待回收实体 */
	protected _recover_entities: EcsEntity[] = [];

	public systems: EcsSystem[] = [];
	protected _systemsMap = new Map<Object, EcsSystem>()


	public groupMap = new Map<string, EcsEntityGroup>();

	private _enable: boolean = false;
	public get enable(): boolean {
		return this._enable;
	}
	public set enable(value: boolean) {
		this._enable = value;
	}
	/**累计完成帧数 */
	public frameCount: number = 0;
	public lockFrame: number = -1;

	public startTimeMs: number = 0;
	public get frameRate() { return Math.round(1000 / this._elapsed_per_frame) }
	/**每帧流逝时间（毫秒） */
	public get elapsedMsPerFrame() { return this._elapsed_per_frame }
	/**
	 * 游戏世界以开始时间为现实时间算的当前时间，如果游戏运行速度不是1，这个时间会比现实时间快或慢
	 */
	public get curTimeMs(): number {
		return this.startTimeMs + this.frameCount * this.elapsedMsPerFrame;
	}
	/**
	 * 游戏世界已流逝的时间(毫秒)
	 */
	public get elapsedTimeMs(): number {
		return this.frameCount * this.elapsedMsPerFrame;
	}

	public constructor(frameRate: number = 59) {
		this._elapsed_per_frame = Math.round(1000 / frameRate);
		this.initSingletons()
		this.initEntityTypes()
		this.initSystems()
	}
	/**初始化单例对象 */
	protected abstract initSingletons(): void
	/**初始化实体类型 */
	protected abstract initEntityTypes(): void
	/**初始化系统 */
	protected abstract initSystems(): void

	public destory(): void {
		for (let k in this.entities) {
			this.removeEntity(this.entities[k].id);
		}

		for (let i = 0; i < this.systems.length; i++) {
			this.systems[i].destroy(this);
		}
	}

	public exit(): void {
		this._exit = true;
	}

	public isExit(): boolean {
		return this._exit;
	}

	/**注册实体类型对象池 */
	public regEntityPool(components: (new () => EcsComponent)[], ...args: any[]): EntityPool {
		const compIds = components.map(comp => EcsComponent.getIdFromClz(comp))
		const groupId = EcsEntity.getGroupID(compIds)
		if (this._entityPoolMap[groupId]) {
			throw Error("register duplicate entity pool:" + groupId);
		}
		let _type = new EntityPool(components, ...args);
		this._entityPoolMap[groupId] = _type;
		return _type;
	}

	/**添加实体 */
	public addEntityByGroupId(groupId: string, cb?: (entity: EcsEntity) => void, id = 0,): EcsEntity {
		if (!id) {
			id = this.nextEntityGenId++
		}
		if (!this._entityPoolMap[groupId]) {
			throw Error("not register entity pool:" + groupId);
		}
		return this.addEntityWithID(id, groupId, cb);
	}

	/** */
	public addEntityWithComps(compClzs: (new () => EcsComponent)[], cb?: (entity: EcsEntity) => void, id = 0): EcsEntity {
		const compIds = compClzs.map(comp => EcsComponent.getIdFromClz(comp))
		const groupId = EcsEntity.getGroupID(compIds)
		if (!this._entityPoolMap[groupId]) {
			this.regEntityPool(compClzs, groupId)
		}
		return this.addEntityByGroupId(groupId, cb, id);
	}

	getEntityPool(groupId: string, compClzs: (new () => EcsComponent)[]): EntityPool {
		let pool = this._entityPoolMap[groupId]
		if (!pool) {
			pool = this.regEntityPool(compClzs, groupId)
		}
		return this._entityPoolMap[groupId]
	}

	/**指定id添加实体 */
	public addEntityWithID(id: number, groupId: string, cb?: (entity: EcsEntity) => void) {
		let _type: EntityPool = this._entityPoolMap[groupId];
		let _entity: EcsEntity = _type.create(id);
		this.entities[_entity.id] = _entity;

		this.groupMap.forEach((group) => {

			if (group.isMatch(_entity)) {
				group.add( _entity);
			}
		})
		cb && cb(_entity)
		if (this.enable) {
			this.afterEntityCreated(_entity);
		}
		return _entity;
	}

	abstract afterEntityCreated(entity: EcsEntity)
	abstract beforeEntityRemove(entity: EcsEntity)

	/**添加单例 */
	public addSingleton<T extends Object>(singleton: T): T {
		this.singletonsMap.set(singleton.constructor, singleton)
		this.singletons.push(singleton)
		return singleton;
	}

	/**获取单例 */
	public getSingleton<T extends any>(singletonClz: (new () => T)) {
		return this.singletonsMap.get(singletonClz) as T;
	}

	/**移除实体 */
	public removeEntity(id: number) {
		let _entity: EcsEntity = this.entities[id];
		if (!_entity) { return; }
		if (this.enable) {
			this.beforeEntityRemove(_entity);
		}
		this.groupMap.forEach((group) => {
			if (group.hasEntity(_entity)) {
				group.remove( _entity);
			}
		})
		if (_entity.destroy()) {
			this._recover_entities.push(_entity);
		}
		//this.entities[id] = null
		delete (this.entities[id])
	}

	public getEntity(id: number): EcsEntity {
		return this.entities[id];
	}

	public getGroupByClz(...clzs: (new () => EcsComponent)[]) {
		return this.getGroupByCompIds(...clzs.map(clz => EcsComponent.getIdFromClz(clz)));
	}

	public getGroupByCompIds(...compIds: number[]) {
		let groupId = EcsEntity.getGroupID(compIds);
		return this.getGroupByGroupId(groupId, ...compIds);
	}
	public getGroupByGroupId(groupId: string, ...compIds: number[]) {
		let group: EcsEntityGroup = this.groupMap.get(groupId);
		if (!group) {
			group = new EcsEntityGroup(compIds, this.entities);
			this.groupMap.set(groupId, group)
		}
		return group;
	}


	public addSystem(system: EcsSystem): void {
		if ((system as EcsSystem)) {
			this._systemsMap.set(system.constructor, system);
			this.systems.push(system as EcsSystem);
		}
	}

	public system<T extends EcsSystem>(systemClz: (new (...args: any) => T)) {
		return this._systemsMap.get(systemClz) as T;
	}

	public synFrame(frame: number): void {
		let _delta_frame = frame - this.frameCount;
		let _delta_elapsed = _delta_frame * 1000 / this.frameRate;
		if (_delta_frame < 0) {
			this.leftElapsed = _delta_elapsed;
		}
		else {
			this.update(_delta_elapsed);
		}
	}

	public update(elapsedMs: number) {
		if (!this.enable) { return; }
		if (this.frameCount == 0) { this.startTimeMs = Date.now() };
		elapsedMs = fixNumber(elapsedMs + this.leftElapsed)

		while (fixNumber(elapsedMs - this._elapsed_per_frame) > 0) {
			if (this.lockFrame >= 0 && this.lockFrame <= this.frameCount) {
				break;
			}
			elapsedMs = fixNumber(elapsedMs - this._elapsed_per_frame)
			this.tickFrame();
		}
		this.leftElapsed = elapsedMs;

	}

	public tickFrame(): void {
		if (!this.enable || this._exit) { return; }
		for (let i = 0; i < this.systems.length; i++) {
			this.systems[i].updateSystem();
		}
		for (let i = 0; i < this._recover_entities.length; i++) {
			this._recover_entities[i].recover();
		}
		this._recover_entities = [];
		this.frameCount++;
	}



	addComponent<T extends EcsComponent>(entity: EcsEntity, compClz: new () => T): T {
		const oldNotMatchedGroups = Array.from(this.groupMap.values()).filter(i => !i.isMatch(entity))
		const compId = (compClz as any).__compId
		const comp = new compClz()
		entity.componentMap.set(compId, comp)
		entity.enabledComponentMap.set(compId, comp)
		const newMatchedGroups = oldNotMatchedGroups.filter(i => i.isMatch(entity))
		newMatchedGroups.forEach(i => i.add( entity))
		const compIds = Array.from(entity.componentMap.keys())
		const compClzs = Array.from(entity.componentMap.values()).map(i => i.constructor as any)
		const groupId = EcsEntity.getGroupID(compIds)
		entity.groupId = groupId
		entity._factory = this.getEntityPool(groupId, compClzs)
		return comp
	}

	removeComponent(entity: EcsEntity, compClz: new () => EcsComponent): void {
		const oldMatchedGroups = Array.from(this.groupMap.values()).filter(i => i.isMatch(entity))
		const compId = (compClz as any).__compId
		entity.componentMap.delete(compId)
		entity.enabledComponentMap.delete(compId)
		const nomoreMatchedGroups = oldMatchedGroups.filter(i => !i.isMatch(entity))
		nomoreMatchedGroups.forEach(i => i.remove( entity))
		const compIds = Array.from(entity.componentMap.keys())
		const compClzs = Array.from(entity.componentMap.values()).map(i => i.constructor as any)
		const groupId = EcsEntity.getGroupID(compIds)
		entity.groupId = groupId
		this.getGroupByGroupId(groupId, ...compIds).add( entity)
		entity._factory = this.getEntityPool(groupId, compClzs)
	}


	// public __onEnableComponent(entity: EcsEntity, id: number): void {
	// 	if (entity.valid) {
	// 		entity.enabledComponentMap.
	// 		let _oldID = entity.__componentsID - Math.pow(2, id);
	// 		let _newID = entity.__componentsID;
	// 		for (let k in this._groups) {
	// 			let _group: EcsEntityGroup = this._groups[k];
	// 			if (_group.isMatch(_newID) && !_group.isMatch(_oldID)) {
	// 				_group.add(this, entity);
	// 			}
	// 		}
	// 	}
	// }

	// public __onDisableComponent(entity: EcsEntity, id: string): void {
	// 	if (entity.valid) {
	// 		let _oldID = entity.__componentsID + Math.pow(2, id);
	// 		let _newID = entity.__componentsID;
	// 		for (let k in this._groups) {
	// 			let _group: EcsEntityGroup = this._groups[k];
	// 			if (_group.isMatch(_oldID) && !_group.isMatch(_newID)) {
	// 				_group.remove(this, entity);
	// 			}
	// 		}
	// 	}
	// }
	// public __onRemoveComponent(entity: EcsEntity,compIds:number[], id: number): void {
	// 	if (entity.valid) {
	// 		let oldCompIds = compIds.concat(id)
	// 		for (let k in this.groupMap) {
	// 			let _group: EcsEntityGroup = this.groupMap[k];
	// 			if (_group.isMatch(entity) && !_group.isMatch(...compIds)) {
	// 				_group.remove(this, entity);
	// 			}
	// 		}
	// 	}
	// }

	// public __onReplaceComponent(entity: EcsEntity, id: number): void {
	// 	if (entity.valid) {
	// 		for (let k in this._groups) {
	// 			let _group: EcsEntityGroup = this._groups[k];
	// 			if (_group.isMatch(entity.__componentsID)) {
	// 				_group.replace(entity, id);
	// 			}
	// 		}
	// 	}
	// }


	public getComponentOwner(comp: EcsComponent) {
		return this.getEntity(comp.__ownerId)
	}

	/**获取两帧之间的时间差(毫秒) */
	msBetweenFrames(startFrame: number, endFrame: number): number {
		return (endFrame - startFrame) * this._elapsed_per_frame;
	}
}

